/*******************************************
 * Copyright 2007 Aphrodite Framework for javaME .
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * Email : owner@aphroditeme.dev.java.net
 * Contributors:
 *     Kent Elton - initial API and implementation
 *******************************************/

package com.aphrodite.ui.widgets;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.CustomItem;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;

import com.aphrodite.util.geom.Rectangle;

/**
 * Generic widget definition.
 * 
 * @author kentelt
 */
public abstract class Widget extends CustomItem {

	protected final static byte IN = 1;
	public final static short LAYOUT_HORIZONTAL = 20;
	public final static short LAYOUT_VERTICAL = 10;

	protected final static byte LOWER = 2;
	protected final static byte UPPER = 0;

	private static Canvas widgetParent;

	/**
	 * <p>
	 * To be called atleast once, before using any of the Widgets.
	 * </p>
	 * It allows the widget toolkit to configure important parameters used by
	 * all Widgets
	 * 
	 * @param canvas
	 *            screen. if its used on canvas only
	 */
	public static void init(Canvas canvas) {
		widgetParent = canvas;
	}

	private int clipHeight;

	private int clipWidth;
	private transient int clipX;
	private transient int clipY;
	protected int height;
	protected boolean isEnabled = true;
	protected boolean isVisible = true;
	private StateListener stateListnr;
	// status tells us where the cursor is on the form, UPPER, IN or LOWER
	protected byte location = UPPER;
	protected Theme theme = Theme.getDefault();
	// protected String title;

	protected int titleHeight;

	/** Widget width */
	protected int width;

	/** Widget x location */
	protected int x = 1;

	/** Widget y location */
	protected int y = 1;

	/**
	 * @param theme
	 *            Theme needs to be set in the constructor. If <code>null</code>
	 *            the default theme is used.
	 * @param width
	 *            of the widget
	 * @param height
	 *            of the widget
	 */
	protected Widget(Theme theme, int width, int height) {
		super(null);
		this.width = width;
		this.height = height;
		this.theme = theme;
	}

	/**
	 * This method performs crutial operations in the widget's rendering cycle.
	 * failing to call this method, would result in,
	 * <ul>
	 * <li>Widget being painted at an invalid location</li>
	 * <li>Widget's background not being painted</li>
	 * <li>Widget's title(if any) not being painted</li>
	 * </ul>
	 * The drawback of this method is that it can only render the background for
	 * perfect rectangular primitives.
	 * 
	 * @param g
	 *            Graphics context
	 * @param width
	 *            for which the background should be drawn
	 * @param height
	 *            for which the background should be drawn
	 * @param drawBG
	 *            draw background for the widget
	 */
	protected void capture(Graphics g, int width, int height, boolean drawBG) {
		clipX = g.getClipX();
		clipY = g.getClipY();
		clipWidth = g.getClipWidth();
		clipHeight = g.getClipHeight();
		g.translate(x, y);
		g.setClip(0, 0, this.width, this.height + titleHeight);
		/* Translate to the location of the widget */
		g.translate(0, titleHeight);
		/* Render the widget background */
		if (drawBG) {
			theme.renderBG(g, width, height);
		}
	}

	/**
	 * The widget cannot be used after this method is called
	 */
	public void cleanUp() {
		theme = null;
	}

	/**
	 * Get the bounds of the component
	 * 
	 * @return Rectangle representing the widget area.
	 * @see Rectangle
	 */
	public com.aphrodite.util.geom.Rectangle getBounds() {
		return new com.aphrodite.util.geom.Rectangle(x, y, width, height);
	}

	/**
	 * 
	 * @return Height of the widget
	 */
	public int getHeight() {
		return height;
	}

	protected final int getMinContentHeight() {
		return isVisible ? height + 2 : 0;
	}

	protected final int getMinContentWidth() {
		return isVisible ? width + 2 : 0;
	}

	protected final int getPrefContentHeight(int width) {
		return isVisible ? height + 2 : 0;
	}

	protected final int getPrefContentWidth(int height) {
		return isVisible ? width + 2 : 0;
	}

	public Theme getTheme() {
		return this.theme;
	}

	/**
	 * Get the width of the widget
	 * 
	 * @return width of the widget
	 */
	public int getWidth() {
		return width;
	}

	/**
	 * Get the Y coordinate of the widget
	 * 
	 * @return Y coordinate of the widget
	 */
	public final int getX() {
		return x;
	}

	/**
	 * Get the X coordinate of the widget
	 * 
	 * @return X coordinate of the widget
	 */
	public final int getY() {
		return y;
	}

	/**
	 * 
	 * @return true, if the widget is visible
	 */
	public boolean isVisible() {
		return isVisible;
	}

	protected final void paint(final Graphics g, final int w, final int h) {
		render(g);
	}

	/**
	 * redraw the full screen
	 */
	protected void reDraw() {
		repaint();
		if (widgetParent != null) {
			widgetParent.repaint();
			widgetParent.serviceRepaints();
		}
		// else
		// log
		// .info(
		// "[Widget] WARNING! Widget.init() should be called from the constructor or showNotify() method of the canvas"
		// );
	}

	/**
	 * redraw the widget only
	 */
	protected void reDrawWidget() {
		repaint(0, 0, width + 2, height + 2);
		if (widgetParent != null)
			widgetParent.repaint(x, y, width + 1, height + 1);
		// else
		// log
		// .info(
		// "WARNING! Widget.init() should be called from the constructor or showNotify() method of the canvas"
		// );
	}

	/**
	 * Release the graphics context occupied by the widget
	 * 
	 * @param g
	 *            Graphics context
	 * @param width
	 *            width of the rendering area
	 * @param height
	 *            height of the rendering area
	 * @param drawBorder
	 *            draw the border for the widget
	 */
	protected final void release(Graphics g, int width, int height, boolean drawBorder) {
		/* Render the title */
		if (super.getLabel() != null) {
			g.translate(0, -titleHeight);
			theme.renderTitle(g, super.getLabel(), width, titleHeight);
		}
		/* Render the border */
		if (drawBorder) {
			theme.renderBorder(g, width, height, titleHeight);
		}
		g.translate(-x, -y);
		g.setClip(clipX, clipY, clipWidth, clipHeight);
	}

	/**
	 * Widget drawing routine
	 * <p>
	 * The <code>render</code> method should.
	 * <ul>
	 * <li>Only perform rendering/layout-change operations.</li>
	 * <li>not perform any blocking operation</li>
	 * <li>be optimized to run as fast as possible</li>
	 * </ul>
	 * </p>
	 * 
	 * @param g
	 *            Graphics instance
	 */
	public abstract void render(Graphics g);

	/**
	 * Forces the repainting of a widget.
	 * <p>
	 * Should be used only when the widget's state needs to be updated and the
	 * widget is unaware of it.<br/> <i>Example</i> <b><br/> Setting the Font
	 * color of a particular cell in a table widget</b><br/>
	 * 
	 * <code>
	 * Table  table =new Table(...);<br/>
	 * TableCell cell = table.getSelectedCell();<br/>
	 * cell.fontColor = 0xff0000;<br/>
	 * <font color="green">//Redraw the table widget.</font><br>
	 * table.repaintWidget();<br/>
	 * </code> <br/> In this example the table widget was unaware of the its
	 * schema being changed. hence a manual repaint is called.
	 * </p>
	 * 
	 */
	public void repaintWidget() {
		reDraw();
	}

	/**
	 * Enable/Disable widget interaction
	 * <p>
	 * If <code>setEnabled(false)</code> is passed the widget cannot be
	 * traversed. In the case of a textbox widget, text cannot be entered or
	 * erased.
	 * </p>
	 * 
	 * @param canEnable
	 */
	public void setEnabled(boolean canEnable) {
		this.isEnabled = canEnable;
		stateChanged(this);
	}

	/**
	 * Set the font for the widget
	 * 
	 * @param font
	 * @throws NullPointerException
	 *             if the font is null
	 */
	public void setFont(Font font, int test) {
		if (font == null) {
			throw new NullPointerException();
		}
		theme.widgetFont = font;
		/* Dont call state changed here */
	}

	/**
	 * 
	 * @see com.aphrodite.ui.widgets.StateListener
	 * @param listener
	 */
	public void setStateListener(StateListener listener) {
		this.stateListnr = listener;
	}

	/**
	 * Set a title for the widget
	 * <p>
	 * When a title is set the height of the widget is increased to accommodate
	 * the height of the title.
	 * </p>
	 * 
	 * @param title
	 *            Widget title. If <code>null</code> no title is rendered
	 */
	public void setTitle(String title) {
		super.setLabel(title);
		// this.title = title;
		setTitleHt();
		reDrawWidget();
	}

	// public void setLabel(String label) {
	//
	// }

	final void setTitleHt() {
		if (super.getLabel() != null)
			titleHeight = (theme.titleFont != null) ? theme.titleFont.getHeight() + 4
					: theme.widgetFont.getHeight() + 4;
		else
			titleHeight = 0;
	}

	/**
	 * Toggle widget visiblity
	 * 
	 * @param isVisible
	 *            flag
	 */
	public void setVisible(boolean isVisible) {
		this.isVisible = isVisible;
		if (!isVisible)
			sizeChanged(0, 0);
		else
			sizeChanged(width, height);
		reDraw();
		stateChanged(this);
	}

	/**
	 * Set the X Coordinate of the widget with respect to the current origin.
	 * <p>
	 * Its important to note that this coordinate is set from the current
	 * origin. If the coordinate system's origin was translated using
	 * <code>translate(x,y)</code> function, The X coordinate is set from that
	 * point
	 * </p>
	 * 
	 * @param x
	 *            coordinate of the widget
	 * @see javax.microedition.lcdui.Graphics#translate(int, int)
	 */
	public void setX(int x) {
		this.x = x;
		reDraw();
		stateChanged(this);
	}

	/**
	 * Set the Y Coordinate of the widget with respect to the current origin.
	 * <p>
	 * <b>Translation</b> Its important to note that this coordinate is set from
	 * the current origin. If the coordinate system's origin was translated
	 * using <code>translate(x,y)</code> function, The Y coordinate is set from
	 * that point.
	 * 
	 * @param y
	 *            coordinate of the widget.
	 * @see javax.microedition.lcdui.Graphics#translate(int, int)
	 */
	public void setY(int y) {
		this.y = y;
		reDraw();
		stateChanged(this);
	}

	/**
	 * Should be called when there is a change in the size/layout/appearance of
	 * the widget. after redraw is called
	 * 
	 * @param source
	 */
	protected void stateChanged(Widget source) {
		if (stateListnr != null) {
			stateListnr.stateChanged(source);
		}
	}

	/**
	 * Called when there are theme changes or any other change that requires the
	 * an update in the widget's model
	 */
	protected abstract void updateModel();
}
